package de.slothsoft.network;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class Server extends Thread {

	public static final int CONNECTIONS_UNLIMITED = -1;
	private static final int IDLE_TIME = 500;

	protected final ServerSocket server;
	private int maximumConnections = CONNECTIONS_UNLIMITED;

	private final List<ServerListener> serverListener = new ArrayList<>();
	private final List<ServerClient> connectedClients = new ArrayList<>();

	public Server(int port) throws NetworkException {
		try {
			this.server = new ServerSocket(port, 2);
		} catch (IOException e) {
			throw new NetworkException(e);
		}
	}

	public void stopServer() throws NetworkException {
		try {
			this.server.close();
		} catch (IOException e) {
			throw new NetworkException(e);
		}
	}

	@Override
	public void run() {
		while (!this.server.isClosed()) {
			if (canAcceptClients()) {
				listenForClients();
			} else {
				idle();
			}
		}
	}

	private boolean canAcceptClients() {
		return this.maximumConnections == CONNECTIONS_UNLIMITED || getCurrentConnections() < this.maximumConnections;
	}

	private void listenForClients() {
		try {
			Socket socket = this.server.accept();
			ServerClient client = createClient(socket);
			client.start();
			this.connectedClients.add(client);
			fireClientConnected(new ServerEvent(client));
		} catch (IOException e) {
			if (!this.server.isClosed()) {
				// if server is closed this exception is okay
				throw new RuntimeException(e);
			}
		}
	}

	protected ServerClient createClient(Socket socket) throws NetworkException {
		return new Client(socket);
	}

	protected void disconnectClient(Client client, IOException e) throws NetworkException {
		client.disconnect(e);
		this.connectedClients.remove(client);
		fireClientDisconnected(new ServerEvent(client, e));
	}

	private static void idle() {
		try {
			Thread.sleep(IDLE_TIME);
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		}
	}

	public int getMaximumConnections() {
		return this.maximumConnections;
	}

	public void setMaximumConnections(int maximumConnections) {
		this.maximumConnections = maximumConnections;
	}

	public void addServerListener(ServerListener listener) {
		this.serverListener.add(listener);
	}

	public void removeServerListener(ServerListener listener) {
		this.serverListener.remove(listener);
	}

	protected void fireClientConnected(ServerEvent event) {
		ServerListener[] array = this.serverListener.toArray(new ServerListener[0]);
		for (ServerListener l : array) {
			l.clientConnected(event);
		}
	}

	protected void fireClientDisconnected(ServerEvent event) {
		ServerListener[] array = this.serverListener.toArray(new ServerListener[0]);
		for (ServerListener l : array) {
			l.clientDisconnected(event);
		}
	}

	public List<ServerClient> getConnectedClients() {
		return this.connectedClients;
	}

	private int getCurrentConnections() {
		return getConnectedClients().size();
	}

}